package com.easylinkin.linkappapi.positioning.service.impl;

import com.alibaba.fastjson.JSONObject;
import com.aliyuncs.IAcsClient;
import com.aliyuncs.dysmsapi.model.v20170525.SendSmsRequest;
import com.aliyuncs.dysmsapi.model.v20170525.SendSmsResponse;
import com.aliyuncs.exceptions.ClientException;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.easylinkin.linkappapi.alarm.mapper.AlarmPersonContactMapper;
import com.easylinkin.linkappapi.common.constant.CommonConstant;
import com.easylinkin.linkappapi.device.entity.Device;
import com.easylinkin.linkappapi.device.entity.DeviceRefAreaScope;
import com.easylinkin.linkappapi.device.mapper.DeviceRefAreaScopeMapper;
import com.easylinkin.linkappapi.gaodemap.entity.ConvertLocationParams;
import com.easylinkin.linkappapi.gaodemap.entity.FenceRelationResult;
import com.easylinkin.linkappapi.gaodemap.entity.FenceResults;
import com.easylinkin.linkappapi.gaodemap.service.GaoDeApiService;
import com.easylinkin.linkappapi.gaodemap.util.GaodeUtils;
import com.easylinkin.linkappapi.openapi.dto.DatapushDTO;
import com.easylinkin.linkappapi.positioning.entity.AlarmConfig;
import com.easylinkin.linkappapi.positioning.entity.AlarmDeviceConfig;
import com.easylinkin.linkappapi.positioning.entity.AlarmPersonContact;
import com.easylinkin.linkappapi.positioning.entity.AlarmSmsMessage;
import com.easylinkin.linkappapi.positioning.entity.ElectronicFence;
import com.easylinkin.linkappapi.positioning.entity.LocationFenceRecord;
import com.easylinkin.linkappapi.positioning.entity.PositionAlarmInfo;
import com.easylinkin.linkappapi.positioning.entity.QueryAlarm;
import com.easylinkin.linkappapi.positioning.mapper.AlarmConfigMapper;
import com.easylinkin.linkappapi.positioning.mapper.AlarmDeviceConfigMapper;
import com.easylinkin.linkappapi.positioning.mapper.ElectronicFenceMapper;
import com.easylinkin.linkappapi.positioning.mapper.LocationFenceRecordMapper;
import com.easylinkin.linkappapi.positioning.mapper.PositionAlarmInfoMapper;
import com.easylinkin.linkappapi.positioning.mapper.SbdwAlarmPersonContactMapper;
import com.easylinkin.linkappapi.positioning.service.PositionAlarmService;
import com.easylinkin.linkappapi.security.context.LinkappUserContextProducer;
import com.easylinkin.linkappapi.security.entity.LinkappUser;
import java.util.ArrayList;
import java.util.Date;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ExecutionException;
import java.util.stream.Collectors;
import javax.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.ObjectUtils;
import org.quartz.Job;
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.StringUtils;

/**
 * class info :定位告警实现类
 *
 * @author liuqihang
 * @date 2021/7/26 11:59
 */
@Service
@Slf4j
@Transactional(propagation = Propagation.REQUIRES_NEW, rollbackFor = Exception.class)
public class PositionAlarmServiceImpl extends ServiceImpl<PositionAlarmInfoMapper, PositionAlarmInfo> implements PositionAlarmService, Job {

    @Resource
    private AlarmConfigMapper alarmConfigMapper;

    @Resource
    private LocationFenceRecordMapper locationFenceRecordMapper;

    @Resource
    private GaoDeApiService gaoDeApiService;

    @Resource
    private ElectronicFenceMapper electronicFenceMapper;

    @Resource
    private AlarmDeviceConfigMapper alarmDeviceConfigMapper;

    @Resource
    private LinkappUserContextProducer linkappUserContextProducer;

    @Value("${message.signName}")
    private String signName;

    @Resource
    private SbdwAlarmPersonContactMapper sbdwAlarmPersonContactMapper;

    @Resource
    private DeviceRefAreaScopeMapper deviceRefAreaScopeMapper;

    @Resource
    private AlarmPersonContactMapper alarmPersonContactMapper;

    @Resource
    private IAcsClient acsClient;

    @Override
    public IPage<PositionAlarmInfo> getPage(Page page, QueryAlarm queryAlarm) {
        LinkappUser current = linkappUserContextProducer.getCurrent();
        queryAlarm.setTenantId(current.getTenantId());
        return baseMapper.getPage(page, queryAlarm);
    }


    @Override
    public void checkPostionAlarm(DatapushDTO jsonData) {

        //设备编码
        String deviceId = jsonData.getDevice_id();
        JSONObject dataJson = jsonData.getData();

        //验证设备是否是车辆定位设备（定位时间、经纬度）
        boolean checkAttributeValueResult = checkAttributeValue(dataJson, deviceId);
        if(!checkAttributeValueResult){
            return;
        }

        //验证设备是否是设备分组设备
        boolean checkDeviceGroupResult = checkDeviceGroup(deviceId);
        if(!checkDeviceGroupResult){
            return;
        }

        //获取设备所归属的电子围栏
        List<ElectronicFence> electronicFenceList = getElectronicFenceListByDeviceCode(deviceId);
        if(ObjectUtils.isEmpty(electronicFenceList)){
            return ;
        }

        //调用高德接口获取地理位置和围栏关系
        try {
            handlerLocationAlarmInfo(deviceId, electronicFenceList, dataJson);
        }catch (Exception e){
            e.printStackTrace();
        }

    }

    public boolean checkAttributeValue(JSONObject dataJson, String deviceId) {
        String positionTime = dataJson.getString("positioning_time");
        if (StringUtils.isEmpty(positionTime)) {
            log.info(deviceId + "的定位时间为空");
            return false;
        }

        String longitude = dataJson.getString("longitude");
        if (StringUtils.isEmpty(longitude)) {
            log.info(deviceId + "的经度为空");
            return false;
        }
        String latitude = dataJson.getString("latitude");
        if (StringUtils.isEmpty(latitude)) {
            log.info(deviceId + "的纬度为空");
            return false;
        }
        return true;
    }

    public boolean checkDeviceGroup(String deviceId){
        DeviceRefAreaScope deviceRefAreaScope = new DeviceRefAreaScope()
                .setDeviceCode(deviceId)
                .setFunctionIdentifier(CommonConstant.DEVICE_POSITIONING);
        List<Device> deviceListByAreaPathAndFunctionIdentifier = deviceRefAreaScopeMapper.getDeviceListByAreaPathAndFunctionIdentifier(deviceRefAreaScope);
        if(ObjectUtils.isEmpty(deviceListByAreaPathAndFunctionIdentifier)){
            return false;
        }
        return true;
    }

    public List<ElectronicFence> getElectronicFenceListByDeviceCode(String deviceId){
        QueryWrapper qw = new QueryWrapper();
        qw.eq("device_code", deviceId);
        List<AlarmDeviceConfig> list = alarmDeviceConfigMapper.selectList(qw);
        if(ObjectUtils.isEmpty(list)){
            return null;
        }
        List<Integer> fenceIdList = list.stream().map(AlarmDeviceConfig::getFenceId).collect(Collectors.toList());
        if(ObjectUtils.isEmpty(fenceIdList)){
            return null;
        }

        QueryWrapper qw2 = new QueryWrapper();
        qw2.in("id", fenceIdList);
        List<ElectronicFence> electronicFenceList = electronicFenceMapper.selectList(qw2);
        return electronicFenceList;
    }

    public void handlerLocationAlarmInfo(String deviceId, List<ElectronicFence> electronicFenceList, JSONObject dataJson) throws ExecutionException, InterruptedException {
        Map<String, List<ElectronicFence>> collect = electronicFenceList.stream().collect(Collectors.groupingBy(ElectronicFence::getGid));

        String longitude = dataJson.getString("longitude");
        String latitude = dataJson.getString("latitude");
        String location = longitude + "," + latitude;

        List<ConvertLocationParams> parameters = new ArrayList<>();
        ConvertLocationParams convertLocationParams = new ConvertLocationParams();
        convertLocationParams.setLocation(location);
        parameters.add(convertLocationParams);

        //GPS坐标转高德
        Map<String, String> resultMap = gaoDeApiService.convertLocation(parameters);
        if(resultMap == null || resultMap.isEmpty()){
            return ;
        }
        //坐标转换后的高德系坐标
        String convertAfterLocation = resultMap.get(location);
        if(StringUtils.isEmpty(convertAfterLocation)){
            return ;
        }

        int size = electronicFenceList.size();
        int baseNum = 100;
        int sunSum = GaodeUtils.getLoopCount(size, baseNum);
        int listStart,listEnd;

        List<FenceResults> singleResult = new LinkedList<>();

        for (int i = 1; i <= sunSum; i++) {
            listStart = (i-1)*baseNum;
            listEnd = listStart + baseNum;
            if(i == sunSum){
                listEnd = size;
            }
            List<ElectronicFence> sunList = electronicFenceList.subList(listStart,listEnd);
            StringBuilder tempStr = new StringBuilder();
            for (ElectronicFence electronicFence:
                    sunList) {
                if (!StringUtils.isEmpty(electronicFence.getGid())) {
                    tempStr.append(",").append(electronicFence.getGid());
                }
            }
            String gfids = tempStr.toString();
            gfids = gfids.substring(1);

            FenceRelationResult fenceRelation = gaoDeApiService.getFenceRelation(convertAfterLocation, gfids, 1, 100);
            if(!fenceRelation.isOK()){
                continue;
            }
            List<FenceResults> results = fenceRelation.getData().getResults();
            singleResult.addAll(results);
        }

        if(ObjectUtils.isEmpty(singleResult)){
            return ;
        }

        Date positioningTime = dataJson.getDate("positioning_time");
        for (FenceResults fenceResults:
            singleResult) {
            Integer fenceId = collect.get(fenceResults.getGfid()).get(0).getId();

            LocationFenceRecord locationFenceRecord = new LocationFenceRecord();
            locationFenceRecord.setDeviceCode(deviceId);
            locationFenceRecord.setFenceId(fenceId);
            locationFenceRecord.setInFlag(fenceResults.getIn());
            locationFenceRecord.setCreateTime(new Date());
            locationFenceRecord.setPositioningTime(positioningTime);
            locationFenceRecordMapper.insert(locationFenceRecord);
        }
    }

    /**
     * 暂不使用，原想的是在保存位置与围栏记录的时候做一个验证（locationFenceRecordMapper.insert前的验证）
     * 验证是否和最后一条上报记录相同，相同则不必存，不同则存
     * @param deviceCode
     * @param fenceId
     * @param inFlag
     * @return  true:存，false:不存
     */
    private boolean isExistsSameLocationFenceRecordEnd(String deviceCode, Integer fenceId, Integer inFlag){
        QueryWrapper qw = new QueryWrapper();
        qw.eq("device_code", deviceCode);
        qw.eq("fence_id", fenceId);
        qw.orderByDesc("create_time");
        List<LocationFenceRecord> list = locationFenceRecordMapper.selectList(qw);
        if(ObjectUtils.isEmpty(list)){
            return true;
        }
        LocationFenceRecord locationFenceRecord = list.get(0);
        if(!inFlag.equals(locationFenceRecord.getInFlag())){
            return true;
        }
        return false;
    }

    @Override
    public void execute(JobExecutionContext jobExecutionContext) throws JobExecutionException {
        //查询位置和围栏记录关系，验证是否触发告警
        log.info("start execute location alarm task ................ start");

        //获取所有电子围栏相关设备
        DeviceRefAreaScope deviceRefAreaScope = new DeviceRefAreaScope()
                .setFunctionIdentifier(CommonConstant.DEVICE_POSITIONING);
        List<Device> deviceList = deviceRefAreaScopeMapper.getDeviceListByAreaPathAndFunctionIdentifier(deviceRefAreaScope);
        List<String> deviceCodeList = deviceList.stream().map(Device::getCode).collect(Collectors.toList());

        //遍历设备
        for (String deviceCode:
                deviceCodeList) {
            //查询设备关联的电子围栏
            List<ElectronicFence> electronicFenceList = getElectronicFenceListByDeviceCode(deviceCode);
            if(ObjectUtils.isEmpty(electronicFenceList)){
                continue;
            }

            for (ElectronicFence electronicFence:
                    electronicFenceList) {
                //获取电子围栏设置的进出告警规则数据
                QueryWrapper qw = new QueryWrapper();
                qw.eq("fence_id", electronicFence.getId());
                List<AlarmConfig> alarmConfigList = alarmConfigMapper.selectList(qw);
                if(ObjectUtils.isEmpty(alarmConfigList)){
                    continue;
                }

                QueryWrapper qw2 = new QueryWrapper();
                qw2.eq("fence_id", electronicFence.getId());
                qw2.eq("device_code", deviceCode);
                qw2.eq("check_status", 0);
                qw2.orderByAsc("positioning_time");
                List<LocationFenceRecord> locationFenceRecordList = locationFenceRecordMapper.selectList(qw2);
                if(ObjectUtils.isEmpty(locationFenceRecordList)){
                    continue;
                }
                List<LocationFenceRecord> endContinuousRecordList = getEndContinuousRecord(locationFenceRecordList);
                //起始位置和围栏关联上报记录
                LocationFenceRecord locationFenceRecord = endContinuousRecordList.get(endContinuousRecordList.size() - 1);

                updateRecordCheckStatus(deviceCode, electronicFence.getId(), locationFenceRecord.getPositioningTime());

                //获取设备、电子围栏记录表数据
                for (AlarmConfig alarmConfig:
                        alarmConfigList) {
                    boolean alarmResult = whetherAlarmByRecord(alarmConfig, endContinuousRecordList);
                    if(alarmResult){
                        DeviceRefAreaScope dras = new DeviceRefAreaScope()
                                .setDeviceCode(deviceCode)
                                .setFunctionIdentifier(CommonConstant.DEVICE_POSITIONING)
                                .setTenantId(electronicFence.getTenantId());
                        List<Device> deviceInfoByFunctionIdentifier = deviceRefAreaScopeMapper.getDeviceInfoByFunctionIdentifier(dras);
                        String areaPath = null;
                        String deviceTypeName = null;
                        if(!ObjectUtils.isEmpty(deviceInfoByFunctionIdentifier)){
                            areaPath = deviceInfoByFunctionIdentifier.get(0).getAreaPath();
                            deviceTypeName = deviceInfoByFunctionIdentifier.get(0).getDeviceTypeName();
                        }
                        AlarmSmsMessage alarmSmsMessage = new AlarmSmsMessage()
                                .setAreaPath(StringUtils.isEmpty(areaPath) ? "未知" : areaPath)
                                .setDeviceTypeName(StringUtils.isEmpty(deviceTypeName) ? "未知" : deviceTypeName)
                                .setDeviceCode(deviceCode)
                                .setTooltip(alarmConfig.getTooltip()).setFenceName(electronicFence.getName());

                        //满足条件保存告警信息，并验证是否已保存过相同告警信息，发送短信
                        saveAlarmInfo(locationFenceRecord.getPositioningTime(),
                                deviceCode, electronicFence.getId(),
                                locationFenceRecord.getId(),alarmConfig,
                                electronicFence.getTenantId(), alarmSmsMessage);
                    }
                }
            }
        }
        log.info("start execute location alarm task ................ end");
    }

    private void saveAlarmInfo(Date positioningTime, String deviceCode, Integer fenceId, Integer recordId, AlarmConfig alarmConfig, String tenantId, AlarmSmsMessage alarmSmsMessage){
        QueryWrapper qw = new QueryWrapper();
        qw.eq("fence_id", fenceId);
        qw.eq("device_code", deviceCode);
        qw.eq("record_id", recordId);
        List<PositionAlarmInfo> list = baseMapper.selectList(qw);
        if(!ObjectUtils.isEmpty(list)){
            log.info("已存在同一段告警记录，不再重复保存");
            return ;
        }
        PositionAlarmInfo positionAlarmInfo = new PositionAlarmInfo()
                .setAlarmTime(positioningTime)
                .setDeviceCode(deviceCode)
                .setFenceId(fenceId)
                .setType(alarmConfig.getType())
                .setRecordId(recordId)
                .setTooltip(alarmConfig.getTooltip())
                .setCreateTime(new Date())
                .setTenantId(tenantId);
        baseMapper.insert(positionAlarmInfo);
        //发送短信
        List<com.easylinkin.linkappapi.alarm.entity.AlarmPersonContact> alarmPersonInfoList = getAlarmPersonInfo(fenceId);
        if(ObjectUtils.isEmpty(alarmPersonInfoList)){
            log.info("fenceId=" + fenceId + "无告警联系人");
            return ;
        }
        for (com.easylinkin.linkappapi.alarm.entity.AlarmPersonContact personContact:
                alarmPersonInfoList) {
            if(!StringUtils.isEmpty(personContact.getPhone())){
                sendCommonSmsNotice(positionAlarmInfo.getId(), personContact.getPhone(), alarmSmsMessage);
            }
        }
    }

    @Async
    public boolean sendCommonSmsNotice(Integer alarmInfoId, String phone, AlarmSmsMessage alarmSmsMessage) {
        SendSmsResponse sendSmsResponse = null;
        SendSmsRequest request = new SendSmsRequest();
        request.setPhoneNumbers(phone);
        request.setTemplateCode(CommonConstant.SMS_221055214);//固定值--车辆告警短信模板唯一编码
        request.setSignName(signName);
        request.setTemplateParam(getParmJsonString(alarmSmsMessage));
        if (null == acsClient) {
            return false;
        }
        try {
            sendSmsResponse = acsClient.getAcsResponse(request);
        } catch (ClientException e) {
            log.error("车辆告警发送短信发生异常 ：" + e.getMessage());
            e.printStackTrace();
        }
        if (null != sendSmsResponse && "OK".equalsIgnoreCase(sendSmsResponse.getCode())) {
            //修改短信发送状态
            PositionAlarmInfo positionAlarmInfo = new PositionAlarmInfo()
                    .setId(alarmInfoId).setSmsStatus(1);
            baseMapper.updateById(positionAlarmInfo);
            return true;
        }
        return false;
    }

    static String getParmJsonString(AlarmSmsMessage alarmSmsMessage) {
        JSONObject param = new JSONObject();
        String areaPath = alarmSmsMessage.getAreaPath();
        if(areaPath.contains(":")){
            areaPath = areaPath.replaceAll(":", "/");
        }
        param.put("areaPath", areaPath);
        param.put("deviceTypeName", alarmSmsMessage.getDeviceTypeName());
        param.put("deviceCode", alarmSmsMessage.getDeviceCode());
        param.put("tooltip", alarmSmsMessage.getTooltip());
        param.put("fenceName", alarmSmsMessage.getFenceName());
        return param.toString();
    }

    private boolean whetherAlarmByRecord(AlarmConfig alarmConfig, List<LocationFenceRecord> endContinuousRecord){
        boolean result = false;

        //告警进出规则是否匹配最新连续状态标识
        boolean matchFlag = false;

        //进入告警
        if(alarmConfig.getType().equals(1)){
            if(endContinuousRecord.get(0).getInFlag().equals(1)){
                matchFlag = true;
            }
        }
        //离开告警
        else if(alarmConfig.getType().equals(2)){
            if(endContinuousRecord.get(0).getInFlag().equals(0)){
                matchFlag = true;
            }
        }

        if(matchFlag){
            Date startTime = endContinuousRecord.get(endContinuousRecord.size()-1).getPositioningTime();
            Date endTime = new Date();
            //时间是否告警
            boolean timeAlarmFlag = whetherAlarm(alarmConfig.getDuration(), startTime, endTime);
            if(timeAlarmFlag){
                result = true;
            }
        }
        return result;
    }

    private boolean whetherAlarm(int duration, Date startTime, Date endTime){
        boolean result = false;

        long minute = (endTime.getTime() - startTime.getTime())/(1000*60);
        if(minute > duration){
            result = true;
        }
        return result;
    }

    /**
     * 获取位置与围栏关系记录最新连续段
     * @param locationFenceRecordList
     * @return
     */
    private List<LocationFenceRecord> getEndContinuousRecord(List<LocationFenceRecord> locationFenceRecordList){
        List<LocationFenceRecord> newLocationFenceRecordList = new ArrayList<>();
        if(locationFenceRecordList.size() == 1){
            newLocationFenceRecordList.add(locationFenceRecordList.get(0));
        }else{
            for (int i = (locationFenceRecordList.size() -1); i > 0 ; i--){
                if(locationFenceRecordList.get(i).getInFlag().equals(locationFenceRecordList.get(i-1).getInFlag())){
                    newLocationFenceRecordList.add(locationFenceRecordList.get(i));
                    if(i == 1){
                        newLocationFenceRecordList.add(locationFenceRecordList.get(i - 1));
                    }
                }else {
                    newLocationFenceRecordList.add(locationFenceRecordList.get(i));
                    break;
                }
            }
        }
        return newLocationFenceRecordList;
    }

    private void updateRecordCheckStatus(String deviceCode, Integer fenceId, Date endTime){
        QueryWrapper qw = new QueryWrapper();
        qw.eq("fence_id", fenceId);
        qw.eq("device_code", deviceCode);
        qw.lt("positioning_time", endTime);
        LocationFenceRecord lfr = new LocationFenceRecord();
        lfr.setCheckStatus(1);

        locationFenceRecordMapper.update(lfr, qw);
    }

    /**
     * 根据围栏ID获取告警联系人列表
     * @param fenceId
     * @return
     */
    private List<com.easylinkin.linkappapi.alarm.entity.AlarmPersonContact> getAlarmPersonInfo(Integer fenceId){
        QueryWrapper qw = new QueryWrapper();
        qw.eq("fence_id", fenceId);

        List<AlarmPersonContact> alarmPersonContactList = sbdwAlarmPersonContactMapper.selectList(qw);
        if(ObjectUtils.isEmpty(alarmPersonContactList)){
            return null;
        }

        List<String> alarmPersonContactIdList = alarmPersonContactList.stream().map(AlarmPersonContact::getAlarmPersonContactId).collect(Collectors.toList());

        QueryWrapper qw2 = new QueryWrapper();
        qw2.in("id", alarmPersonContactIdList);
        List<com.easylinkin.linkappapi.alarm.entity.AlarmPersonContact> list = alarmPersonContactMapper.selectList(qw2);
        return list;
    }

}
